using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;

namespace Fadd.Components
{
    /// <summary>
    /// This class can scan dependencies to find components and load them.
    /// </summary>
    /// <remarks>
    /// How do we know the interface that a component want to register with?
    /// </remarks>
    public class ComponentLoader
    {
        private List<ComponentTypeInfo> _components = new List<ComponentTypeInfo>();

        /// <summary>
        /// Information about all found components.
        /// </summary>
        public List<ComponentTypeInfo> Components
        {
            get { return _components; }
        }

        /// <summary>
        /// Add all componentManager to the specified component manager.
        /// </summary>
        /// <param name="componentManager"></param>
        public void AddAllComponents(IComponentManager componentManager)
        {
            foreach (ComponentTypeInfo info in _components)
            {
                if (componentManager.Contains(info.InterfaceType))
                    continue;

                if (info.IsPrivate)
                    componentManager.AddPrivate(info.InterfaceType, info.InstanceType);
                else
                    componentManager.AddType(info.InterfaceType, info.InstanceType);
            }
        }

        /// <summary>
        /// Add all components found in the specified assemblies.
        /// </summary>
        /// <param name="assemblies"></param>
        public void AddComponents(List<Assembly> assemblies)
        {
            foreach (Assembly assembly in assemblies)
            {
                AddComponentsFromAssembly(assembly);
            }
        }

        private void AddComponentsFromAssembly(Assembly assembly)
        {
            AssemblyName assemblyName = assembly.GetName();
            byte[] publicKey = assemblyName.GetPublicKey();
            foreach (Type type in assembly.GetTypes())
            {
                foreach (object attribute in type.GetCustomAttributes(true))
                {
                    if (!(attribute is ComponentAttribute))
                        continue;

                    ComponentAttribute componentAttribute = (ComponentAttribute) attribute;
                    Components.Add(new ComponentTypeInfo(assembly.Location, publicKey, type, componentAttribute.InterfaceType));
                }
            }
        }

        


        /// <summary>
        /// Find components in the specified path.
        /// </summary>
        /// <param name="fullPath">Path and wild cards.</param>
        /// <exception cref="ReflectionTypeLoadException"></exception>
        /// <exception cref="TypeLoadException"></exception>
        public void Find(string fullPath)
        {
            AppDomain domain = AppDomain.CreateDomain("ModuleLoader");

            ComponentLoader finder = (ComponentLoader)domain.CreateInstanceFromAndUnwrap(
                                                    GetType().Assembly.Location ?? string.Empty, GetType().Namespace + ".ComponentLoader");

            finder.FindInternal(fullPath);
            _components = finder.Components;
            AppDomain.Unload(domain);
        }

        /// <summary>
        /// Finds the specified full path.
        /// </summary>
        /// <param name="fullPath">Path and wild cards.</param>
        /// <exception cref="TypeLoadException"></exception>
        protected void FindInternal(string fullPath)
        {
            string searchString;
            string directory;
            if (fullPath.Contains("."))
            {
                int pos = fullPath.LastIndexOf('\\');
                if (pos == -1)
                {
                    searchString = fullPath;
                    directory = Environment.CurrentDirectory;
                }
                else
                {
                    searchString = fullPath.Substring(pos + 1);
                    directory = fullPath.Substring(0, pos);
                }
            }
            else
            {
                searchString = "*.dll";
                directory = fullPath;
            }

            foreach (string fileName in Directory.GetFiles(directory, searchString))
            {
                try
                {
                    Assembly assembly;
                    try
                    {
                        assembly = Assembly.LoadFrom(fileName);
                    }
                    catch (FileLoadException err)
                    {
                        Console.WriteLine(err);
                        continue;
                    }

                    AddComponentsFromAssembly(assembly);
                }
                catch (ReflectionTypeLoadException e)
                {
                    throw new TypeLoadException("Couldn't load component: " + fileName, e);
                }
            }
        }
    }
}
